package com.kingdee.exam.judger.application;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.kingdee.exam.judger.core.Dispatcher;
import com.kingdee.exam.judger.messenger.MessageSender;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.kingdee.exam.judger.mapper.JudgeResultMapper;
import com.kingdee.exam.judger.mapper.SubmissionMapper;
import com.kingdee.exam.judger.model.JudgeResult;
import com.kingdee.exam.judger.model.Submission;

/**
 * 应用程序调度器.
 */
@Component
public class ApplicationDispatcher {
	/**
	 * 收到消息队列的新的评测请求时的回调函数.
	 * @param submissionId - 评测记录的唯一标识符
	 */
	public void onSubmissionCreated(long submissionId) {
		try {
			judgerDispatcher.createNewTask(submissionId);
		} catch (Exception ex) {
			LOGGER.catching(ex);
		}
	}
	
	/**
	 * 当系统错误发生时通知用户.
	 * @param submissionId - 评测记录的唯一标识符
	 */
	public void onErrorOccurred(long submissionId) {
		updateSubmission(submissionId, 0, 0, 0, "SE", "发生内部错误。");
		
		Map<String, Object> mapMessage = new HashMap<>();
		mapMessage.put("event", "ErrorOccurred");
		mapMessage.put("submissionId", submissionId);
		
		messageSender.sendMessage(mapMessage);
	}
	
	/**
	 * 当编译阶段结束时通知用户.
	 * @param submissionId - 评测记录的唯一标识符
	 * @param result - 编译结果
	 */
	public void onCompileFinished(long submissionId, Map<String, Object> result) {
		boolean isSuccessful = (Boolean)result.get("isSuccessful");
		String log = getJudgeLog(result);

		if ( !isSuccessful ) {
			updateSubmission(submissionId, 0, 0, 0, "CE", log);
		}
		Map<String, Object> mapMessage = new HashMap<>();
		mapMessage.put("event", "CompileFinished");
		mapMessage.put("submissionId", submissionId);
		mapMessage.put("isSuccessful", isSuccessful);
		mapMessage.put("log", log);
		
		messageSender.sendMessage(mapMessage);
	}
	
	/**
	 * 实时返回评测结果.
	 * @param submissionId - 提交记录的编号
	 * @param checkpointId - 测试点的编号
	 * @param runtimeResult - 某个测试点的程序运行结果
	 */
	public void onOneTestPointFinished(long submissionId, int checkpointId, Map<String, Object> runtimeResult) {
		String runtimeResultSlug = getRuntimeResultSlug(runtimeResult);
		String runtimeResultName = getRuntimeResultName(runtimeResultSlug);
		int usedTime = getUsedTime(runtimeResult);
		int usedMemory = getUsedMemory(runtimeResult);
		int score = getScore(runtimeResult);
		
		Map<String, Object> mapMessage = new HashMap<>();
		mapMessage.put("event", "TestPointFinished");
		mapMessage.put("submissionId", submissionId);
		mapMessage.put("checkpointId", checkpointId);
		mapMessage.put("runtimeResult", runtimeResultName);
		mapMessage.put("usedTime", usedTime);
		mapMessage.put("usedMemory", usedMemory);
		mapMessage.put("score", score);
		
		messageSender.sendMessage(mapMessage);
	}
	
	/**
	 * 持久化程序评测结果
	 * @param submissionId - 提交记录的编号
	 * @param runtimeResults - 对各个测试点的评测结果集
	 */
	public void onAllTestPointsFinished(long submissionId, List<Map<String, Object>> runtimeResults) {
		int totalTime = 0;
		int maxMemory = 0;
		int totalScore = 0;
		String runtimeResultSlug = "AC";
		String log;
		
		for ( Map<String, Object> runtimeResult : runtimeResults ) {
			String currentRuntimeResultSlug = getRuntimeResultSlug(runtimeResult);
			int usedTime = getUsedTime(runtimeResult);
			int usedMemory = getUsedMemory(runtimeResult);
			int score = getScore(runtimeResult);
			
			totalTime += usedTime;
			if ( usedMemory > maxMemory ) {
				maxMemory = usedMemory;
			}
			if ( "AC".equals(currentRuntimeResultSlug) ) {
				totalScore += score;
			}
			if ( !"AC".equals(currentRuntimeResultSlug) ) {
				runtimeResultSlug = currentRuntimeResultSlug;
			}
		}
		log = getJudgeLog(runtimeResults, runtimeResultSlug, totalTime, maxMemory, totalScore);
		updateSubmission(submissionId, totalTime, maxMemory, totalScore, runtimeResultSlug, log);
		
		Map<String, Object> mapMessage = new HashMap<>();
		mapMessage.put("event", "AllTestPointsFinished");
		mapMessage.put("submissionId", submissionId);
		mapMessage.put("runtimeResult", getRuntimeResultName(runtimeResultSlug));
		mapMessage.put("totalTime", totalTime);
		mapMessage.put("maxMemory", maxMemory);
		mapMessage.put("totalScore", totalScore);
		
		messageSender.sendMessage(mapMessage);
	}
	
	/**
	 * 从评测结果集中获取程序评测结果的唯一英文缩写.
	 * @param runtimeResult - 程序评测结果 
	 * @return 程序评测结果的唯一英文缩写
	 */
	private String getRuntimeResultSlug(Map<String, Object> runtimeResult) {
		Object runtimeResultObject = runtimeResult.get("runtimeResult");
		
		if ( runtimeResultObject == null ) {
			return "SE";
		}
		return (String)runtimeResultObject;
	}
	
	/**
	 * 获取评测结果的全称.
	 * @param runtimeResultSlug - 评测结果的唯一英文缩写
	 * @return 评测结果的全称
	 */
	private String getRuntimeResultName(String runtimeResultSlug) {
		JudgeResult judgeResult = judgeResultMapper.getJudgeResultUsingSlug(runtimeResultSlug);
		
		if ( judgeResult == null ) {
			return "System Error";
		}
		return judgeResult.getJudgeResultName();
	}
	
	/**
	 * 从评测结果集中获取程序运行时间(ms).
	 * @param runtimeResult - 程序评测结果
	 * @return 程序运行时间(ms)
	 */
	private int getUsedTime(Map<String, Object> runtimeResult) {
		Object usedTimeObject = runtimeResult.get("usedTime");
		
		if ( usedTimeObject == null ) {
			return 0;
		}
		return (Integer)usedTimeObject;
	}
	
	/**
	 * 从评测结果集中获取内存使用量(KB).
	 * @param runtimeResult - 程序评测结果
	 * @return 内存使用量(KB)
	 */
	private int getUsedMemory(Map<String, Object> runtimeResult) {
		Object usedMemoryObject = runtimeResult.get("usedMemory");
		
		if ( usedMemoryObject == null ) {
			return 0;
		}
		return (Integer)usedMemoryObject;
	}
	
	/**
	 * 从评测结果集中获取测试点对应的分值
	 * @param runtimeResult - 程序评测结果
	 * @return 测试点对应的分值
	 */
	private int getScore(Map<String, Object> runtimeResult) {
		Object scoreObject = runtimeResult.get("score");
		
		if ( scoreObject == null ) {
			return 0;
		}
		return (Integer)scoreObject;
	}
	
	/**
	 * 格式化编译时日志.
	 * @param result - 包含编译状态的Map<String, Object>对象
	 * @return 格式化后的日志
	 */
	private String getJudgeLog(Map<String, Object> result) {
		boolean isSuccessful = (Boolean)result.get("isSuccessful");
		String compileLog = (String)result.get("log");

		StringBuilder formatedLogBuilder = new StringBuilder();
		formatedLogBuilder.append(String.format("编译 %s.\n\n", isSuccessful ? "成功" : "出错"));
		if ( !isSuccessful ) {
			formatedLogBuilder.append(compileLog.replace("\n", "\n\n"));
			formatedLogBuilder.append("\n编译出错, 所用时间 = 0 ms, 所用内存 = 0 KB, 得分 = 0.\n");
		}
		return formatedLogBuilder.toString();
	}
	
	/**
	 * 格式化运行时日志.
	 * @param runtimeResults - 对各个测试点的评测结果集
	 * @param runtimeResultSlug runtimeResults结果集的别名
	 * @param totalTime 总用时
	 * @param maxMemory 最大内存
	 * @param totalScore 总分
	 * @return 运行日志
	 */
	private String getJudgeLog(List<Map<String, Object>> runtimeResults, 
			String runtimeResultSlug, int totalTime, int maxMemory, int totalScore) {
		int checkpointId = -1;
		String runtimeResultName = getRuntimeResultName(runtimeResultSlug);
		
		StringBuilder formatedLogBuilder = new StringBuilder();
		formatedLogBuilder.append("编译成功.\n\n");
		for ( Map<String, Object> runtimeResult : runtimeResults ) {
			String currentRuntimeResultSlug = getRuntimeResultSlug(runtimeResult);
			String currentRuntimeResultName = getRuntimeResultName(currentRuntimeResultSlug);
			int usedTime = getUsedTime(runtimeResult);
			int usedMemory = getUsedMemory(runtimeResult);
			int score = getScore(runtimeResult);
			
			if ( !"AC".equals(currentRuntimeResultSlug) ) {
				score = 0;
			}
			formatedLogBuilder.append(String.format("- 测试用例 #%d: %s, 所用时间 = %d ms, 所用内存 = %d KB, 得分 = %d\n",
                    ++ checkpointId, currentRuntimeResultName, usedTime, usedMemory, score));
		}
		formatedLogBuilder.append(String.format("\n%s, 所用时间 = %d ms, 所用内存 = %d KB, 得分 = %d\n",
                runtimeResultName, totalTime, maxMemory, totalScore));
		return formatedLogBuilder.toString();
	}
	
	/**
	 * 更新提交记录信息.
	 * @param submissionId - 提交记录的唯一标识符
	 * @param usedTime - 提交运行使用时间(所有时间之和)
	 * @param usedMemory - 提交运行使用内存(最大内存占用)
	 * @param score - 运行得分
	 * @param judgeResult - 运行结果(JudgeResultSlug)
	 * @param log - 运行日志记录
	 */
	private void updateSubmission(long submissionId, int usedTime, 
			int usedMemory, int score, String judgeResult, String log) {
		Submission submission = submissionMapper.getSubmission(submissionId);
		submission.setExecuteTime(new Date());
		submission.setUsedTime(usedTime);
		submission.setUsedMemory(usedMemory);
		submission.setJudgeScore(score);
		submission.setJudgeResultSlug(judgeResult);
		submission.setJudgeLog(log);
		
		submissionMapper.updateSubmission(submission);
	}
	
	/**
	 * 用于完成评测作业的任务调度.
	 */
	@Autowired
	private Dispatcher judgerDispatcher;
	
	/**
	 * 用于向消息队列发送消息.
	 */
	@Autowired
	private MessageSender messageSender;

	/**
	 * 用于查询/更新提交记录的相关信息.
	 */
	@Autowired
	private SubmissionMapper submissionMapper;
	
	/**
	 * 用于全部评测结果的信息.
	 */
	@Autowired
	private JudgeResultMapper judgeResultMapper;
	
	/**
	 * 日志记录器.
	 */
	private static final Logger LOGGER = LogManager.getLogger(ApplicationDispatcher.class);
}
